define(function(require, exports, module) {
	/**
	 * @class DraggableMenu
	 * @memberof Nuui
	 * @classdesc 可以拖动排序的格子菜单, 有编辑删除的功能<br/>
	 * 		基本元素data:<br/>
	 * 		data应该包含属性: 基本数据和url, 基本数据作为渲染item的内容, url作为跳转的路径<br/>
	 * 		可以设置最后一个data的属性static为true来禁止它拖动和排序<br/>
	 * 		修改样式的方法:<br/>
	 * 		激活的item的ClassName:"DrM-activeItem"<br/>
	 * 		排序的item的ClassName:"DrM-reorderItem", 例子: 若要隐藏排序item, 可以添加样式如".DrM-reorderItem{opacity:0.01}"<br/>
	 * 		拖动的item的ClassName:"DrM-draggingItem"<br/>
	 * 		操作事件说明:<br/>
	 * 		点击item --> 跳转页面<br/>
	 * 		长按item --> 进入编辑模式 --> 松开item --> 点击任何item, 退出编辑模式<br/>
	 * 		长按item --> 进入编辑模式 --> 松开item --> 点击关闭按钮, 删除编辑的item, 退出编辑模式<br/>
	 * 		长按item --> 进入编辑模式 --> 拖拽item --> 在新位置松开item --> items重新排序<br/>
	 * @param {$} container - 菜单容器的jQuery对象
	 * @param {object} config - 配置
	 * @param {int} config.pressDuration - 长按事件的时间间隔<br/>
	 * @param {int} config.cssDuration - 排序动画的时间长度<br/>
	 * @param {int} config.rangXY - 允许触控的边缘值, 单位px<br/>
	 * @param {array} config.dataList - 渲染html的基本数据<br/>
	 * @param {func} config.renderer - 自定义html模板渲染页面, 接收参数1为单个数据, 参数2为该数据在列表的序号<br/>
	 * @param {func} config.dataList - 渲染item的html的方法<br/>
	 * @param {func} config.onItemTap(data) - 正常点击事件<br/>
	    onItemTap方法是在非编辑状态的点击执行事件, 可通过第一参数获得本item在dataList里的数据data<br/>
		建议在本方法里执行跳转的方法<br/>
	 * @param {func} config.onDragEnd($items) - 拖放后的事件<br/>
	    onDragEnd方法是在拖拽更新排序后执行的事件, 可通过第一参数获取的items重新排序后的jQuery对象集合$items<br/>
	    建议在本方法里把items新的信息发送请求更新服务器<br/>
	 * @param {func} config.onClose($items) - 删除item的事件<br/>
	    onClose方法是在点击关闭按钮, 删除item后执行的事件, 可通过第一参数获取剩余在页面的items的jQuery对象集合$items<br/>
	    建议在本方法里把items新的信息发送请求更新服务器<br/>
	 * @param {func} config.onEditing($item) - 进入编辑模式的事件<br/>
	    onDragEnd方法是在长按item进入编辑状态时执行的事件, 可通过第一参数获得本item对象$item<br/>
	    建议在本方法里执行原生事件例如震动, 或执行对本item的操作<br/>
	 * @example new DraggableMenu($(".draggableMenu"), {
	 * 	dataList: [
	 * 		{text:'信用卡', icon:"c", id:"credit", url:"example/index/mycountDetail"},
	 * 		{text:'理财交易', icon:"d", id:"product", url:"example/index/iwant"},
	 * 		{text:'生活缴费', icon:"e", id:"life", url:"example/index/lifePayment"},
	 * 		{text:'周边', icon:"g", id:"bankNodes", url:"example/index/bankNodes"},
	 * 		{text:'话费充值', icon:"h", id:"recharge", url:"example/index/recharge"},
	 * 		{text:'无卡取款', icon:"i", id:"", url:""},
	 * 		{text:'组件展示', icon:"d", id:"toComponentsList", url:"demo/demo/list"},
	 * 		{text:'添加更多', icon:"f", id:"", url:"", static: true}
	 * 	],
	 * 	renderer: function (data, i) {
	 * 		return $('<li>').addClass('dragItem cell').attr({'id': data.id})
	 * 			.append($("<i class='list-ico'>").addClass(data.icon))
	 * 			.append($('<span>').text(data.text))
	 * 	},
	 * 	onItemTap: function (data) {
	 * 		// 建议在本方法里执行跳转的方法
	 * 		App.navigate(data.url, true);
	 * 	},
	 * 	onDragEnd: function (data) {
	 * 		// 建议在本方法里把items新的信息发送请求更新服务器
	 * 	},
	 * 	onClose: function (data) {
	 * 		// 建议在本方法里把items新的信息发送请求更新服务器
	 * 	},
	 * 	onEditing: function (data) {
	 * 		// 建议在本方法里执行原生事件例如震动, 或执行对本item的操作
	 * 	}
	 * });
	 */

	var DraggableMenu = module.exports = function ($container, config, view) {

		this.initialize($container,config, view);

		return this;
	};

	DraggableMenu.prototype = {
		initialize: function($container, options, view) {
			this._$DOM = view ? view.$el : $(document);

			// 绑定方法的上下文
			this._startEventFunc = $.proxy(this._startEventFunc, this);
			this._dragEventFn = $.proxy(this._dragEventFn, this);
			this._stopEventFunc = $.proxy(this._stopEventFunc, this);

			// 属性设置
			this._config = $.extend({}, this._defaultConfig, options);

			/*模式选择*/
			var mode = this._staticConfig._mode[this._staticConfig._modeSelect];
			for(var xx in mode.attr ){
				this._staticConfig[xx] = mode.attr[xx];
			}

			// 检测环境选择属性
			this._setProps();

			this._$container = $container.css({'position': 'relative', "padding": 0});

			// 生成到DOM树里
			if(this._staticConfig._templateRender && this._config.dataList.length){
				this._renderItems();
			}

			// 获取尺寸数据
			this._getSize();

			// 计算静态位置数组与items的序号数组
			this._getPosAry_getIndexAry();

			// items对应位置对齐
			this._setItemsPos(this._$items);

			//this._$cloneItems = this._$items.clone().prependTo(this._$container).children().css('visibility', 'hidden');
			//
			//this._$items.css('border', 'none');

			this._$container.on(this._startEvent,'.' + this._config.closeBtnClassName, $.proxy(this._clickCloseBtnFn, this));

			// 延迟使用transition, 避免初始化的生成html所带有的动画
			setTimeout($.proxy(function(){
				this._applyTransition(this._$items, this._config.reorderDuration);
			}, this), 1);

			// 绑定点击事件
			this._$items.on(this._startEvent, this._startEventFunc);
		},

		/*
		 * 默认设置
		 * */
		_defaultConfig: {
			// 关闭按钮的className
			closeBtnClassName: "DrM-closeBtn",

			// 长按的时间间隔
			pressDuration: 300,
			// 排序效果动画的过度时间transition-duration值
			reorderDuration: 300,
			// 放大效果动画的过度时间transition-duration值
			focusDuration: 80,
			// 允许触控的边缘值, 单位px
			rangeXY: 4,

			// 渲染html的数据内容
			dataList: [],

			// 使用放大效果, 基于perspective
			usePerspective: null,

			// 渲染html的方法
			renderer: function(data, i, datas){
				// 本方法提供给用户修改, 但要求必须返回html字符串作为每个item的内容
				return $('<li>').addClass('dragItem').append(
					$('<div>')
						.attr({'id': data.id})
						.append($("<i class='list-ico'>").addClass(data.icon))
						.append($('<span>').text(data.text))
				);
			},

			// 公开事件: 正常点击事件
			onItemTap: null,
			// 公开事件: 拖放后的事件
			onDragEnd: null,
			// 公开事件: 删除item的事件
			onClose: null,
			// 公开事件: 进入编辑模式的事件
			onEditing: null
		},

		/**
		 * 容器对象
		 */
		_$container: null,
		/**
		 * items集合
		 */
		_$items: null,
		/**
		 * 排序对象
		 */
		_$reorderItem: null,
		/**
		 * 拖拽对象
		 */
		_$draggingItem: null,
		/**
		 * 点击对象
		 */
		_$touchTarget:null,
		/**
		 * 编辑对象
		 */
		_$editItem:null,

		/**
		 * item尺寸
		 */
		_itemW: null,
		_itemH: null,

		/**
		 * 容器尺寸
		 */
		_containerW: null,
		_containerH: null,

		/**
		 * 容器列数行数
		 */
		_containerCols: null,
		_containerRows: null,

		/**
		 * 事件类型
		 */
		_hasTouch: null,
		_startEvent: null,
		_stopEvent: null,
		_moveEvent: null,

		/**
		 * 状态: _dragging是进入touchMove的状态, sensitive模式下可能不需要
		 */
		_dragging: false,
		/**
		 * 状态: editing编辑模式是针对长按状态里添加"添加或删除"按钮进行编辑, 逻辑是长按进入编辑状态
		 */
		_editing: false,

		/*
		 * touchStart的坐标
		 * */
		_eventStartX: null,
		_eventStartY: null,

		/*
		 * reorderItem现在的位置序号, 也是作为进入编辑模式的item所在视觉位置序号
		 * */
		_reorderItemIndex: null,

		/*
		 * reorderItem视觉位置
		 * */
		_visualIndex: null,

		/*
		 * touchStart时间点
		 * */
		_startTime: null,

		/*
		 * 拖拽的初始化状态
		 * */
		_InitializeMoveEvent: false,

		/*
		 * 定时器
		 * */
		_setTimeFunc: null,

		/*
		 * 环境是否支持Transitions
		 * */
		_cssTransitions: null,
		/*
		 * 环境是否支持transforms
		 * */
		_transformsEnabled: null,

		/*
		 * css属性transition/transform/translate前缀
		 * */
		_transitionType: null,
		_transformType: null,
		_animType: null,

		/*
		 * 不可拖动与可拖动的数量
		 * */
		_staticCount: 0,
		_draggableCount: 0,

		/*
		 * 各item文本位置的数组, 有顺序
		 * */
		_indexAry: null,

		/*
		 * 静态位置的坐标数组
		 * */
		_posAry: null,

		/*
		 * 点击目标的原始数据
		 * */
		_$touchTargetData: null,

		/*
		 * 拖拽item有没有引发排序, 因支付宝效果所需要的状态
		 * */
		_dragToReorder: false,

		/*
		 * 固定设置, jun的开发配置
		 * */
		_staticConfig :{
			// class名称
			// 激活的item, 包括拖动的item和排序的item
			activeItemClass: "DrM-activeItem",
			// editingItem的幽灵状态, 这个状态是在dragItem存在的时候才有的
			ghostItemClass: "DrM-reorderItem",
			// 拖动的item
			draggingItemClass: "DrM-draggingItem",
			// 编辑中的item
			editingItemClass: "DrM-editingItem",
			// dragItem在释放拖拽一瞬间到回归位置的状态
			reItemClass: "DrM-reItem",

			_modeSelect: 'mode3',

			_mode: {
				'mode2': {
					attr: {
						_reorderTransition: true,
						_useTransform: false // _useTransform必须为false来使用css定位才可以提供用户自定义keyframes
					},
					name: 'Float-css',
					desc: '项目模式: item浮动排序的基础, 用户可自定义item的keyframes动画, 特点:1, 排序的效果有过度; 2, 指定用户自己来写keyframes '
				},
				'mode3': {
					attr: {
						_reorderTransition: true,
						_useTransform: true,
						_animation: true// 可删除的属性, 因为mode3模式应该直接使用本属性, 但要具体看修改代码时候的情况
					},
					name: 'Float-translate',
					desc: '最优动画模式: item浮动排序的基础, 用户定义item的keyframes动画的话需要在config里定义, 特点:1, 排序的效果有过度; 2, 指定用户在config来添加keyframes; 3, 动画效果有最好的效果. 模式3是最麻烦的模式, 因为使用了translate定位是影响到transform的其他属性的使用, 所以在放大效果需要scale的话就需要本组件自己设定好transform里translate值与scale同步'
				}
			},

			// 灵敏模式, 准备删除
			_sensitive: true,
			// 选择模板, 看是否能删除, 应该可以, 但不用, 因为只需要保留true值就可以, 意思是只需要用户传值渲染数据都会使用自定义模板
			_templateRender: true,
			// _useCSS的正否是选择translate3D还是translate, 当然最后会由环境来判断, 这里一直默认是true
			_useCSS: true,
			// 选择transition的动画效果属性
			_transitionTiming: "ease-in-out",
			// 点击时间间隔
			_clickDuration: 250,

			/*模式属性, 默认为模式2*/
			// _animation的正否决定是否由本组件负责生产keyframes, 默认否
			_animation: false,
			// 选择transform动画来定位, 当使用translate定位的话会影响到keyframes的自定义使用
			_useTransform: false
		},
		_renderItems: function(){
			// 填充template内容并收集所有item的html的jQuery包装集
			var data = this._config.dataList,
				len = data.length,
				$itemHtml, $itemsHtml = [];

			for(var i = 0; i < len; i++){
				$itemHtml = this._config.renderer(data[i], i, data)// 根据用户的自定义模板填进数据
					.data('DrM-dataDetail', data[i]);
					//.data('DrM-index', i);
				if(data[i].static){
					$itemHtml.addClass('DrM-static');
					this._staticCount++;// 记数
				}
				$itemsHtml.push($itemHtml);// ps: 假设static项写在数组的最后
			}

			// 把所有item的html的jQuery包装集渲染到容器里
			this._$container.html($itemsHtml);
		},

		_getSize: function(){
			this._$items = this._$container.children();

			// 获取子项li尺寸
			this._itemH = this._$items.outerHeight(true);
			this._itemW = this._$items.outerWidth(true);

			// 获取容器ul宽度尺寸
			this._containerW = this._$container.width();

			// 计算容器的列数和行数
			//this._containerCols = Math.floor(this._containerW / this._itemW);
			// 遍历方法来计算容器列数, 方法是计算第i个换行的,那i就是列数, 这方法的意义是按照css设计者的样式计算
			for(var i = 0; i < this._$items.length; i++){
				if(this._$items.eq(i).position().top > 1){
					this._containerCols = i;
					break;
				}
			}
			this._containerCols = this._containerCols || this._$items.length;

			this._containerRows = Math.ceil(this._$items.length / this._containerCols);

			// 锁定容器尺寸
			this._$container.css({
				'height': this._containerH = this._containerRows * this._itemH,
				'width' : this._containerW,
				'overflow': 'hidden'
			});
			this._$items.css({position: 'absolute', left: 0, top: 0})
		},

		// 根据容器的尺寸计算出一个数组, 长度为items.length, 内容是格子左上角坐标
		_getPosAry_getIndexAry: function(){
			// 位置的静态写法
			// 数组保存:格子数量和各格子坐标, 优点: 避免重复计算
			this._posAry = [];
			// 默认基于translate3D的修改模式, 所以升级必须优化

			// 获取初始排序的数组, 以item文本位置序号为内容的数组
			this._indexAry = [];

			for(var i = 0; i < this._$items.length; i++){
				this._indexAry[i] // 视觉位置
					= i; // i是文本位置的序号

				var position = {};
				var inRow = Math.floor(i / this._containerCols);
				var inCol = i % this._containerCols;
				position.left = inCol * this._itemW;
				position.top = inRow * this._itemH;
				this._posAry.push(position);

				this._indexAry[i] // 视觉位置
					= i; // i是文本位置的序号
			}
		},

		_setItemsPos: function($items, index1, index2){
			// index1, index2作为选择性执行的范围
			var len, st = 0;

			if(index1 && index2 && index1 !== index2){
				if(index1 > index2){
					st = index2;
					len = index1 + 1;
				} else {
					st = index1;
					len = index2 + 1;
				}
			} else {
				len = $items.length;
			}

			for(var i = st; i < len; i++){
				this._setPosition($($items[i]), this._posAry[i])
			}
		},

		_startEventFunc :function(event){
			this._startTime = event.timeStamp || +new Date();

			if(
				event.target.className == this._config.closeBtnClassName || // 拖点击对象是关闭按钮, 则不能执行本方法
				(this._stopTime && (this._startTime - this._stopTime) < this._config.reorderDuration) // 离上一次操作完毕太短时间
			){
				console.log('点击关闭按钮或距离上一次操作太快'); return
			}

			this._$touchTarget = $(event.currentTarget);

			// 记录初始位置
			this._eventStartX = this._page('x', event);
			this._eventStartY = this._page('y', event);

			this._textIndex =
				this._$touchTarget.addClass(this._staticConfig.activeItemClass)// 进入激活模式
					.index();// 获取文本位置的序号

			// 由于DOM结构固定, 所以需要在变量indexAry数组里获取DOM-index所在的视觉位置序号
			this._visualIndex = $.inArray(this._textIndex, this._indexAry);

			this._draggingItemStartPos = this._posAry[this._visualIndex];

			// 获取本DOM的原始数据
			if(this._config.dataList){
				this._$touchTargetData = this._$touchTarget.data('DrM-dataDetail');//this._config.dataList[this._visualIndex];
			}

			// 绑定事件_stopEvent, 本方法必须在绑定拖拽事件之前
			this._$DOM.one(this._stopEvent, this._stopEventFunc);

			if(!this._$touchTargetData || (this._$touchTargetData && !this._$touchTargetData.static)){
				// 设定时触发press, 因按下后到一定时间, 即使没有执行什么都会执行press和进行编辑模式
				var _this = this;

				this._setTimeFunc = setTimeout(function(){
					event.preventDefault();
					_this._enterEditMode();
					_this._renderDragItem();
				}, this._config.pressDuration);

				// 绑定拖拽事件
				this._$DOM.on(this._moveEvent, this._dragEventFn);
			}

		},


		/*
		 进入编辑模式:
		 touchItem --> editItem
		 数据: reorderItemIndex = visualIndex
		 取消editItem的transition
		 */
		_enterEditMode: function(){
			// 在编辑模式中, 再次进入编辑模式的话, 若不是原本对象, 先把原本对象转为正常item
			if(this._editing && this._reorderItemIndex !== this._visualIndex){
				this._$editItem.removeClass(this._staticConfig.editingItemClass);
			}

			// 设为编辑模式
			this._editing = true;

			// 排序位置reorderItemIndex为touchItem的视觉位置, 为了让关闭按钮可以通过reorderItemIndex来删除位置
			this._reorderItemIndex = this._visualIndex;

			// 提供外部执行的方法
			this._config.onEditing(this._$items, this._$touchTarget);

			// touchTarget 转为 editItem
			this._$editItem = this._$touchTarget.addClass(this._staticConfig.editingItemClass);

			// 停止动画, 因为进入编辑模式的item需要立即的效果切换, 如透明度, 立即显示与立即隐藏
			this._disableTransition(this._$editItem);
		},

		/*
		 退出编辑模式:
		 editItem --> item
		 数据清空
		 恢复editItem的transition
		 */
		_quitEditMode: function(){
			if(!this._editing){return}

			// 需要隔开时间, 先让css的透明度立即呈现
			var _this = this, target = this._$editItem;
			setTimeout(function(){
				_this._applyTransition(target);
			},20);

			this._$editItem.removeClass(this._staticConfig.editingItemClass);

			this._$editItem = null;

			this._reorderItemIndex = null;

			this._editing = false;
		},

		_renderDragItem: function(){
			// 生成dragItem
			if(!this._$editItem){return}

			this._$draggingItem = this._$editItem.clone()
				.removeClass(this._staticConfig.editingItemClass)
				.addClass(this._staticConfig.draggingItemClass)
				.css({'z-index':'1001'})
				.appendTo(this._$container);

			// editItem --> ghostItem
			this._$editItem.addClass(this._staticConfig.ghostItemClass);

			// 动画放大dragItem
			this._$draggingItem.position();// 这没实际用处, 但可成功使用transition, 否则没有渐变效果!! 重要发现!
			this._applyTransition(this._$draggingItem, this._config.focusDuration);// 缩短dragItem的transitionDuration
			this._setPosition(this._$draggingItem, this._posAry[this._visualIndex], {scale: '1.2'}); // 设dragItem的scale为1.2倍
		},

		/*
		 * 先dragItem --> Item
		 * 动画回归dragItem后执行callback
		 * */
		_removeDragItem: function(callback){
			if(!this._editing){return}

			var _this = this;
			// dragItem在释放触控的一刻转为普通item
			this._$draggingItem.removeClass(this._staticConfig.draggingItemClass).addClass(this._staticConfig.reItemClass);
			// 动画回归
			this._applyTransition(this._$draggingItem);
			this._setPosition(this._$draggingItem, this._posAry[this._reorderItemIndex], 1);

			// 动画效果后的callback
			setTimeout(function(){

				_this._$draggingItem.remove();

				// ghostItem  --> editItem
				_this._$editItem.removeClass(_this._staticConfig.ghostItemClass);

				if(callback){callback()}

			}, this._config.reorderDuration);
		},

		_clickCloseBtnFn: function(e){
			//console.log('格子序号', this._reorderItemIndex);
			// 说明: 变量reorderItemIndex是当前进行编辑模式的item所在视觉位置

			this._stopTime = e.timeStamp || +new Date();

			//console.log('删除item对象内容 ',
			// 删除reorderItemsAry里视觉位置的item
			this._$items.splice(this._reorderItemIndex, 1)
			//[0]);
			//console.log('删除后, 更新的对象集', this._$items);

			// 删除
			this._indexAry.splice(this._reorderItemIndex, 1);

			// 遍历更新
			for (var y = 0; y < this._indexAry.length; y++){
				var indexValue = this._indexAry[y];
				if(indexValue > this._textIndex){
					this._indexAry[y] = indexValue - 1;
				}
			}

			// 调整容器的高度为适当高度
			this._$container.height(
				Math.ceil(this._$items.length / this._containerCols) * this._itemH
			);

			// 删除本item
			this._$editItem.remove();

			this._quitEditMode();

			// 提供外部执行的方法, 传参修改后的items对象集合
			this._config.onClose(this._$items);

			// 清空排序的序号, 否则长按与本_reorderItemIndex值相同的视觉位置item会没有反应
			this._reorderItemIndex = null;

			// 动画"定位"剩下的items
			this._setItemsPos(this._$items);
			this._editing = false;
		},

		_cleanEvent: function(){
			// 清空绑定事件与定时器, 清空由startEvent于moveEvent放生的状态与事件

			// 退出激活模式
			this._$items.removeClass(this._staticConfig.activeItemClass);

			clearTimeout(this._setTimeFunc);

			this._$DOM.off(this._moveEvent + " " + this._stopEvent);

			this._dragging = false;// 这属性都不在这里使用, 先关闭

			this._InitializeMoveEvent = false;// 这属性都不在这里使用, 先关闭

		},

		_stopEventFunc: function(event){

			this._stopTime = event.timeStamp || +new Date();

			var isPress = (this._stopTime - this._startTime) > this._config.pressDuration, _this = this;

			this._startTime = this._stopTime;// 方便判断双击

			this._cleanEvent();

			if(isPress && this._editing){
				// 情景: 有编辑模式就必然有dragItem
				// 以下两种区分是按照支付宝效果:
				if(this._dragToReorder){
					// 情景: 编辑模式且拖拽产生排序
					this._removeDragItem(function(){//callback
						// 取消编辑状态
						_this._quitEditMode();
						// 提供外部的方法, 传参排序后的jQuery对象集合
						_this._config.onDragEnd(_this._$items);
						// 清理拖拽排序情况
						_this._dragToReorder = null;// ?? 应该放在哪里的
					});

				} else {
					// 情景: 编辑模式且拖拽没有产生排序, 应该在reset dragItem后保留编辑状态
					this._removeDragItem();
				}

			} else if(!isPress){

				if(!this._editing){ // 判断: 没有拖拽后且没有滑动且只在限制时间内才是click事件
					// 状态: 非编辑模式且没有拖拽的点击, 是正常的点击
					this._config.onItemTap(this._$touchTargetData);

					event.preventDefault();

				} else {

					this._quitEditMode();
				}
			}
		},

		_dragEventFn: function(event){
			this._dragging = true;// 进入拖动模式

			var Move_ex = this._page('x', event),
				Move_ey = this._page('y', event);

			// 初始化MoveEvent
			if(!this._InitializeMoveEvent){

				if(this._staticConfig._sensitive){
					// 灵敏模式, 只关心满足时间条件就可以拖拽
					var inShort = (event.timeStamp - this._startTime) < this._config.pressDuration;

					if (inShort){
						this._cleanEvent();
						return;
					}
				} else {
					this._sensitiveJudge(event);
				}

				// 满足两个条件后, 初始化(仅进行一次)
				this._InitializeMoveEvent = true;
				// 重新获取可以拖拉的数量
				this._draggableCount = this._$items.length - this._staticCount;

				// 进入拖拽状态前必须先清空reorderItem的transition, 因为需要reorderItem立即变化为透明与在释放dragItem动画后立即显示reorderItem
				this._disableTransition(this._$editItem);

				// 清空transition来实现无延迟拖拽
				this._disableTransition(this._$draggingItem);
			}

			// 在初始化拖动后才禁止默认事件行为
			event.preventDefault();

			var cssX, cssY;
			// 计算触控点拖拽距离
			cssX = Move_ex - this._eventStartX;
			cssY = Move_ey - this._eventStartY;

			// 计算item被拖拽时的坐标
			cssX = this._draggingItemStartPos.left + cssX;
			cssY = this._draggingItemStartPos.top + cssY;

			// 拖拽
			this._setPosition(this._$draggingItem, {'left': cssX, 'top': cssY}, {scale: '1.2'});

			// 重新排序
			this._reorder(cssX, cssY);
		},

		_getTouchIndex: function(touchX, touchY){
			// 不能超出容器范围
			if(touchX > 0 && touchX <= this._containerW && touchY > 0 && touchY <= this._containerH){
				var curCol = Math.floor(touchX / this._itemW) + 1;// 列数
				var curRow = Math.floor(touchY / this._itemH);// 行数
				return (curRow * this._containerCols + curCol - 1);// 计算值 = (坐标行数-1)*容器列数 + 坐标列数 -1;
			}
		},

		_reorder: function(cssX, cssY) {
			/* 思路1: 监听触控点位置来插入空白格子 */
			// 1, 计算触控点位置
			// 2, 计算target的文档位置
			// 3, 以1与2的相对位置, 整除_itemW和_itemH得出触控点所在的li的序号index, 以此作为插入的位置
			// 但Bug!!! 缩放屏幕会出现偏差. 根本原因是步骤1与2的获取位置的原理不同, 缩放时各自变化比例不同, 所以不能同时使用思路1

			/* 思路2: 监听拖动项的中心位置来插入空白格子 */
			// 1, 计算拖拽时target中心位置的坐标targetCenterPos
			var targetCenterPosX = cssX + this._itemW / 2,
				targetCenterPosY = cssY + this._itemH / 2;

			// 2, 以targetCenterPos坐标来计算触控点所在视觉位置visionIndex
			var visionIndex = this._getTouchIndex(targetCenterPosX, targetCenterPosY) || 0;

			// 3, 选择性的进行排序
			// 基于绝对定位, 不用考虑文本流的插入index值的调整
			if(
				visionIndex !== this._reorderItemIndex && // 在同一item上的拖拽不执行重新排序
				visionIndex >= 0 && visionIndex < this._draggableCount // 超过items数量范围的拖拽不执行重新排序
			){
				this._dragToReorder = true;
				// 重新排序数组
				this._reorderFn(this._$items, this._reorderItemIndex, visionIndex);
				this._reorderFn(this._indexAry, this._reorderItemIndex, visionIndex);

				// 重新排序items位置, 只对有视觉上需要位移的items进行排序
				this._setItemsPos(this._$items, this._reorderItemIndex, visionIndex);

				// 更新本次位置
				this._reorderItemIndex = visionIndex;
			}
			// 对比思路1, 由于拖拽距离是稳定的, 判断插入的位置只是基于文档位置的获取机制, 所以可以.
		},

		_reorderFn: function(targetAry, reorderItemIndex, newIndex){
			// 抽出
			var reorderItem = targetAry.splice(reorderItemIndex, 1)[0];
			// 指定插入
			targetAry.splice(newIndex, 0, reorderItem);
		},


		/*-----------------------------------------------------------------------------------------------*/
		/*-----------------------------------------------------------------------------------------------*/
		/*-----------------------------  以下方法可另作组件公用  -----------------------------------------*/
		/*-----------------------------------------------------------------------------------------------*/
		/*-----------------------------------------------------------------------------------------------*/

		_applyTransition: function($obj, duration) {
			// 添加css  Transition
			var transition = {};

			// 默认过渡时间是排序过渡时间
			duration = duration || this._config.reorderDuration;

			transition[this._transitionType] = 'all ' + duration + 'ms ' + this._staticConfig._transitionTiming;
			//transition[this._transitionType] = this._transformType + ' ' + this._config.reorderDuration + 'ms ' + this._staticConfig._transitionTiming;

			$obj.css(transition);
		},

		_disableTransition: function($obj) {
			// 去掉css  Transition
			var transition = {};

			transition[this._transitionType] = 'all 0s';
			//transition[this._transitionType] = '';

			$obj.css(transition);
		},

		_setProps: function() {
			// 环境检测可用的css属性: 能否使用transition, 能否使用transform

			var bodyStyle = document.body.style;

			// 选择事件类型, 添加命名空间, 不会与其他插件冲突
			this._hasTouch = 'ontouchstart' in window;
			this._startEvent = this._hasTouch ? 'touchstart.draggableMenu': 'mousedown.draggableMenu';
			this._stopEvent = this._hasTouch ? 'touchend.draggableMenu': 'mouseup.draggableMenu';
			this._moveEvent = this._hasTouch ? 'touchmove.draggableMenu': 'mousemove.draggableMenu';

			if (bodyStyle.WebkitTransition !== undefined ||
				bodyStyle.MozTransition !== undefined ||
				bodyStyle.msTransition !== undefined) {
				if (this._staticConfig._useCSS === true) { //_config是提供用户的选择, 但要使用的话, 需检测环境能否
					this._cssTransitions = true;
				}
			}
			/*setProps的主要作用之一:检测可使用的前缀, 可以用来借鉴, Perspective更小众*/
			if (bodyStyle.OTransform !== undefined) {
				this._animType = 'OTransform';
				this._transformType = '-o-transform';
				this._transitionType = 'OTransition';
				this._animationType = '-o-animation';
				if (bodyStyle.perspectiveProperty === undefined && bodyStyle.webkitPerspective === undefined) this._animType = false;
			}
			if (bodyStyle.MozTransform !== undefined) {
				this._animType = 'MozTransform';
				this._transformType = '-moz-transform';
				this._transitionType = 'MozTransition';
				this._animationType = '-moz-animation';
				if (bodyStyle.perspectiveProperty === undefined && bodyStyle.MozPerspective === undefined) this._animType = false;
			}
			if (bodyStyle.webkitTransform !== undefined) {
				this._animType = 'webkitTransform';
				this._transformType = '-webkit-transform';
				this._transitionType = 'webkitTransition';
				this._animationType = '-webkit-animation';
				if (bodyStyle.perspectiveProperty === undefined && bodyStyle.webkitPerspective === undefined) this._animType = false;
			}
			if (bodyStyle.msTransform !== undefined) {
				this._animType = 'msTransform';
				this._transformType = '-ms-transform';
				this._transitionType = 'msTransition';
				this._animationType = '-ms-animation';
				if (bodyStyle.msTransform === undefined) this._animType = false;
			}
			if (bodyStyle.transform !== undefined && this._animType !== false) {
				this._animType = 'transform';
				this._transformType = 'transform';
				this._transitionType = 'transition';
				this._animationType = 'animation';
			}
			this._transformsEnabled = this._staticConfig._useTransform && (this._animType !== null && this._animType !== false);
			//this._transformsEnabled = false;// 测试用
			//this._cssTransitions = false;// 测试用
		},

		_setPosition: function($obj, position, option) {
			// 方法setCSS: 即时位置调整
			// 之后扩展可以参考scale来做
			option = option || {};
			var positionProps = {},
				x, y,
				scale = option.scale || '1';

			x =  Math.ceil(position.left) + 'px';
			y =  Math.ceil(position.top) + 'px';

			if (this._transformsEnabled === false) {
				positionProps = {'left': x, "top": y};
				//scale = "scale(" + scale + ', ' + scale + ")";
				//positionProps[this._animType] = scale;
			} else {
				// 配置scale, 提供用户使用放大效果
				if (this._cssTransitions === false) {
					scale = "scale(" + scale + ', ' + scale + ")";
					positionProps[this._animType] = 'translate(' + x + ', ' + y + ') ' + scale;
				} else {
					scale = "scale3d(" + scale + ', ' + scale + ', ' + scale + ")";
					positionProps[this._animType] = 'translate3d(' + x + ', ' + y + ', 0px) ' + scale;
				}
			}
			//console.log('positionProps', positionProps);
			$obj.css(positionProps);
		},

		// 方法: 获取触控点坐标
		_page :  function (coord, event) {
			return (this._hasTouch? event.originalEvent.touches[0]: event)['page' + coord.toUpperCase()];
		}
	};

});
